package webagent

import (
	"github.com/garyburd/redigo/redis"
	"net"
//	"encoding/json"
//	"net/http"
//	"os"
//	"io"
	"zsystem/maintain"
//	"flag"
	"errors"
	"fmt"
//	"regexp"
	"strings"
	"strconv"
	"time"
	"log"
//	"errors"
)

const (
	EXIT_FLAG_MAIN_LOOP				= 1
	EXIT_FLAG_HEART_BEAT_LOOP = 2
	EXIT_FLAG_ALL = EXIT_FLAG_MAIN_LOOP | EXIT_FLAG_HEART_BEAT_LOOP
)

type WebAgentData struct {
	RedisData *maintain.RedisData
	CmdChan *chan string
	Maps *maintain.Maps
	ExitFlags int
	timestampUpdateAt int64
	reconnecting bool
}

func (data *WebAgentData) CopyConn(other *WebAgentData) {
	data.RedisData = other.RedisData
	data.CmdChan = other.CmdChan
	data.Maps = other.Maps
	data.ExitFlags = other.ExitFlags
	data.timestampUpdateAt = other.timestampUpdateAt
}

func Conn(serverAddress string, cmdChan *chan string) (data *WebAgentData, err error) {
	var srvAddr *net.TCPAddr
	srvAddr, err = net.ResolveTCPAddr("tcp4", serverAddress)
	if err!= nil { return }
	return ConnByAddress(srvAddr, cmdChan)
}

func ConnByAddress(srvAddr *net.TCPAddr, cmdChan *chan string) (data *WebAgentData, err error) {
	err = nil
	if cmdChan == nil { 
		err = errors.New("ConnByAddress() Param error, cmdChan is nil")
		return
	}
	data, err = conn(srvAddr)
	if err != nil { 
		return
	}

	(*data).CmdChan = cmdChan
	fmt.Printf("data: %+v\n", *data)
	return
}

func conn(srvAddr *net.TCPAddr) (data *WebAgentData, err error) {
	err = nil
	data = new(WebAgentData)
	(*data).RedisData, err = maintain.ConnByAddress(srvAddr)
	if err != nil { 
		defer data.Close(); 
		return
	}

	err = data.update()
	if err != nil { 
		err = errors.New("webagent.conn(): update error:" + err.Error())
		defer data.Close(); 
		return
	}
	return
}

func (data *WebAgentData) Close() {
	if (*data).RedisData != nil {
		(*data).RedisData.Close()
		(*data).RedisData = nil
	}
}

func (data *WebAgentData) Reconn() (err error) {
	if (*data).RedisData == nil {
		err = errors.New("webagent.Reconn() error, Can't reconnect after close")
		return
	} else {
		(*data).RedisData.Close()
	}

	var newData *WebAgentData
	newData, err = conn((*(*data).RedisData).ServerAddress)
	if err != nil {	fmt.Println("webagent::Reconn() conn() Failed! error:", err); return }
	data.CopyConn(newData)
	return
}

func (data *WebAgentData) update() (err error) {
	maps := new(maintain.Maps)
	err = (*data).RedisData.UpdateMaps(maps)
	if err != nil { 
		err = errors.New("WatchDogData::update(): RedisData.UpdateMaps() error:" + err.Error())
		return
	}
//	maps.Dump()
	(*data).Maps = maps
	return 
}

func (data *WebAgentData) keepRunning() bool {
	return (data.ExitFlags & EXIT_FLAG_MAIN_LOOP) == 0
}

func (data *WebAgentData) GetRooms(roomIds *[]string) (rooms *maintain.RoomMap) {
	roomMap := (*(*data).Maps).RoomMap
	tmpMap := make(maintain.RoomMap, 100)
	for _, roomId := range *roomIds {
		switch {
		case roomId == "*":
			for key, value := range *roomMap {
				tmpMap[key] = value
			}
		case strings.Index(roomId, "!") == 0:
			rmId, err := strconv.Atoi(strings.TrimLeft(roomId, "!"))
			if err == nil {
				delete(tmpMap, rmId)
			}
		default:
			id, err := strconv.Atoi(roomId)
			if err == nil {
				value, found := (*roomMap)[id]
				if found {
					tmpMap[id] = value
				}
			}
		}
	}
	return &tmpMap
}

func (data *WebAgentData) GetRoom(roomId int) (room *maintain.Room, isNew bool) {
	var found bool
	var err error
	isNew = false
	room = nil
	maps := data.Maps
	redisData := data.RedisData
	room, found = (*maps.RoomMap)[roomId]
	if !found {
		isNew = true
		// TODO: new room
		var newServerId int
		newServerId, err = redis.Int((*redisData.Conn).Do("LPOP", "rslist"))
		if err != nil {
			log.Println("LPOP rslist error: ", err)
			return
		}
		// Get new server
		var server *maintain.Server
		server, found = (*maps.ServerMap)[newServerId]
		if !found {
			log.Println("Can not found server: ", newServerId)
			_, err = (*redisData.Conn).Do("LPUSH", "rslist", strconv.Itoa(newServerId))
			return
		}

		var num int
		num, err = redis.Int((*redisData.Conn).Do("SADD", "rlist", strconv.Itoa(roomId)))
		if err != nil {
			log.Println("SADD rlist error: ", err)
			return
		}
		if num == 0 {
			// Room added after last update
			// return error, retry later
			log.Println("Room added after last update")
			return
		}

		_, err = (*redisData.Conn).Do("HMSET", "room:" + strconv.Itoa(roomId), "srvId", strconv.Itoa(newServerId),
												"startAt", time.Now().Unix() + data.RedisData.TimeDiff, "type", "1")
		if err != nil {
			log.Println("HMSET room error: ", err)
			return
		}

		room = new(maintain.Room)
		room.Id = roomId
		room.StartAt = int(time.Now().Unix() + data.RedisData.TimeDiff)
		room.Server = server
	}
	return
}


func (data *WebAgentData)  heartBeatLoop() {
	pre := time.Now().Unix()
	for data.keepRunning() {
		now := time.Now().Unix()
		if pre + 5 > now {
			time.Sleep(300 * time.Microsecond)
			continue
		} else {
			fmt.Println("before send __hb__")
			(*(*data).CmdChan) <- "__hb__"
			fmt.Println("after send __hb__")
			pre = now
		}
	}
	(*(*data).CmdChan) <- "__hb_exit__"
}

func (data *WebAgentData) MainLoop() {
	data.ExitFlags = 0

	// Start heart beat loop
	go data.heartBeatLoop()

	var err error

	for data.ExitFlags != EXIT_FLAG_ALL {
		// Waiting command
		fmt.Println("before waiting command")
		cmd := <- (*data.CmdChan)
		fmt.Println("after waiting command")
		cmds := strings.Split(cmd, ",")
		switch strings.ToLower(cmds[0]) {
			case "exit":
				log.Println("Received exit command")
				data.ExitFlags |= EXIT_FLAG_MAIN_LOOP
			case "__hb_exit__":
				log.Println("__hb_exit__ command")
				data.ExitFlags |= EXIT_FLAG_HEART_BEAT_LOOP
			case "__hb__":
				err = data.heartBeat()
				if err != nil {
					log.Println("Heartbeat error: ", err)
					go data.reconnLoop()
				}
			case "dump":
				(*(*data).Maps).Dump()
			default:
				log.Println("Unknown command received:", cmd)
		}
	}
}

func (data *WebAgentData) heartBeat() (err error){
	now := time.Now().Unix()
	if (*data).timestampUpdateAt + 60 < now {
		// Update timestamp diff
		err = (*data).RedisData.UpdateTimestamp()
		if err != nil { return }
		(*data).timestampUpdateAt = now
	}

	// Update all
	err = data.update()
	if err != nil {
		log.Println("heartbeat update from redis failed")
		return
	}
	return 
}

func (data *WebAgentData) reconnLoop(){
	if data.reconnecting { return }
	data.reconnecting = true
	var err error
	wait := time.Second
	for {	
		err = data.Reconn()
		if err != nil {
			// Reconnect failed, retry
			time.Sleep(wait)
			if wait < 32 {
				wait = wait * 2
			}
		} else {
			break
		}
	}
	data.reconnecting = false
}
